package neuronvisualizer;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.JFrame;
import javax.swing.JMenuBar;

import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.IRenderer;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;



public class Visual extends JFrame{
	
	//3D SCENE VARIABLES
		private World visualizer;
		
	//ORIGINAL MODELS
		private Object3D RS;
		private Object3D LTS;
		private Object3D FS;
		private Object3D IB;
		
	
	//CAMERA VARIABLES
		private SimpleVector ORIGIN = new SimpleVector(0,0,0);
		private SimpleVector CAMDEFAULT = new SimpleVector(0,-100,-400);
		
	//OPEN GL VARIABLES
		private static FrameBuffer buffer;
	
	//NEURON VARIABLES
		private ArrayList<Object3D> models;
		private static ArrayList<Object3D> sections;
		private static HashMap<Integer,Neuron> neurons;
		private static HashMap<Integer,Object3D> neuronModels;
		private ParseNeurons parser;
		
		//private ArrayList<Object3D> connections;
		
	//OTHER VARIABLES
		private static boolean running;
	
	public Visual() throws InterruptedException{
		
		/*
		 * Configure Settings
		 */
			Config.glWindowName="Neuron Network Visualizer";
			Config.maxPolysVisible = 100000;
			
            //Alex: added the following two lines
            //The first line makes it so that when a texture's image data is transferred to the graphics card,
            //a copy will not be kept in the Texture object; should free up some memory, but will crash if we switch
            //to software rendering
            Config.glAvoidTextureCopies = true;
            //If the following is enabled, jPCT will reserve potentially needed memory only if it is REALLY needed
            Config.saveMemory = true;

			
		/*
		 * Load Textures
		 */
			loadTextures();
			createModels();
			
		/*
		 * Initialize Neuron variables
		 */
			neurons = new HashMap<Integer,Neuron>(100);
			neuronModels = new HashMap<Integer,Object3D>(100);

			models=new ArrayList<Object3D>();
			sections=new ArrayList<Object3D>();
			parser = new ParseNeurons(this,"/neurons.txt");
		
		/*
		 * Initialize 3D Scene
		 */

			visualizer=new World();
			visualizer.setAmbientLight(75,75,75);
			ORIGIN = parser.midPoint();
			CAMDEFAULT = new SimpleVector(ORIGIN.x,ORIGIN.y,ORIGIN.z-400);
			visualizer.getCamera().setPosition(CAMDEFAULT);
			visualizer.getCamera().lookAt(ORIGIN);
			visualizer.addLight(new SimpleVector(ORIGIN.x,-300,ORIGIN.z),new Color(150,150,150));
			visualizer.setLightAttenuation(0, -3);
			
			
		/*
		 * Build connection models
		 */
			int totalNeurons = models.size();
			for(int i=0;i<totalNeurons-1;i++){
				models.add(connect((Object3D)models.get(i),(Object3D)models.get(i+1),(float)Math.random()*4));
			}
			
			for(int i=0;i<((parser.maxX-parser.minX)/200);i++){
				sections.add(divider(i));
				sections.add(highlight(i));
			}
			
		
		/*
		 * Add objects to 3d Scene
		 */
			for(int nc = 0;nc<models.size();nc++){
				visualizer.addObject((Object3D)models.get(nc));
			}	
			for(int nc = 0;nc<sections.size();nc++){
				visualizer.addObject((Object3D)sections.get(nc));
			}
			visualizer.buildAllObjects();		
		
			
		
		buffer = new FrameBuffer(640, 480, FrameBuffer.SAMPLINGMODE_NORMAL);
		buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
		buffer.enableRenderer(IRenderer.RENDERER_OPENGL);
		
			
		this.setBounds(0, 0, 640,480);
		
		startLoop();
	}
	
	public void startLoop(){
		long ticks=0;
		running = true;
		
		while (!org.lwjgl.opengl.Display.isCloseRequested() && running) {
			ticks++;
			
			if(ticks%2==0){
				checkKeyboard();
			}
			checkMouse();
		
			
	        buffer.clear(java.awt.Color.BLACK);
			visualizer.renderScene(buffer);
			visualizer.draw(buffer);
			buffer.update();
			buffer.displayGLOnly();
			
			
			//GIVES THE VISUALIZATION ROUGHLY 30 FPS
			try{Thread.sleep(33);}catch(Exception e){} 
			
		}
		
		closeVisual();
		
		
	}
	
	public static void closeVisual(){
		running = false;
		try{
			buffer.disableRenderer(IRenderer.RENDERER_OPENGL);
			buffer.dispose();
			System.exit(0);
		}catch(Exception e){}
	}
	
	public void checkKeyboard(){
		if(Keyboard.isKeyDown(Keyboard.KEY_R) && Keyboard.getEventNanoseconds()<1000){
			visualizer.getCamera().setPosition(CAMDEFAULT);
			visualizer.getCamera().lookAt(ORIGIN);
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_1)&& Keyboard.getEventNanoseconds()<500){
			toggleType("RS");
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_2)){
			toggleType("FS");
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_3)){
			toggleType("LTS");
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_4)){
			toggleType("IB");
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_S)){
			toggleSections();
		}
	}
	public void checkMouse(){
		int dx = Mouse.getDX();
		int dy = Mouse.getDY();
		int dw = Mouse.getDWheel();
		
		if(dx!=0 && Mouse.isButtonDown(1)){
			if(dx<0){
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVERIGHT,10f);
			}
			else{
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVELEFT,10f);
			}
		}
		if(dy!=0 && Mouse.isButtonDown(1)){
			if(dy<0){
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVEUP, 10f);
			}
			else{
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVEDOWN,10f);
			}
		}	
	
		
		if(dx!=0 && Mouse.isButtonDown(0)){
			if(dx<0){
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVERIGHT,25f);

				visualizer.getCamera().lookAt(ORIGIN);
			}
			else{
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVELEFT,25f);
				visualizer.getCamera().lookAt(ORIGIN);
			}
		}
		if(dy!=0 && Mouse.isButtonDown(0)){
			if(dy<0){
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVEDOWN, 25f);
				visualizer.getCamera().lookAt(ORIGIN);
			}
			else{
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVEUP,25f);
				visualizer.getCamera().lookAt(ORIGIN);
			}
		}
		
		
		if(dw!=0){
			if(dw>0){
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVEIN,40f);
			}
			else{
				visualizer.getCamera().moveCamera(Camera.CAMERA_MOVEOUT,40f);
			}
		}
	}
	
	public static void toggleSections(){
		for(int i=0;i<sections.size();i++){
			Object3D tempModel = (Object3D)sections.get(i);
			tempModel.setVisibility(!tempModel.getVisibility());
			
		}
	}
	
	public static void toggleType(String s){
		for(Integer i=0;i<neurons.size();i++){
			Neuron t = (Neuron)neurons.get(i);
			if(t.type().equals(s)){
				Object3D tempModel = (Object3D)neuronModels.get(i);
				tempModel.setVisibility(!tempModel.getVisibility());
			}
		}
	}
	
	
	public void loadTextures(){
		TextureManager.getInstance().addTexture("blue",new Texture(10,10,new Color(181,221,255)));
		TextureManager.getInstance().addTexture("white",new Texture(10,10,new Color(255,255,255)));
		TextureManager.getInstance().addTexture("gray",new Texture(10,10,new Color(164,164,164)));
		TextureManager.getInstance().addTexture("red",new Texture(10,10,new Color(228,4,4)));
		TextureManager.getInstance().addTexture("dblue",new Texture(10,10,new Color(0,97,172)));
		TextureManager.getInstance().addTexture("green",new Texture(10,10,new Color(54,220,38)));
		TextureManager.getInstance().addTexture("yellow",new Texture(10,10,new Color(255,255,0)));
		
		TextureManager.getInstance().addTexture("glow",new Texture(this.getClass().getResourceAsStream("resource/glow.png"),true));
		
	}
	
	/*
	 * Supposedly creating one model and making new ones clone the existing model is better for performance
	 * So this function creates the base model for all 4 neuron types.
	 */
	public void createModels(){		
		RS = Primitives.getPyramide(5f);
		RS.setShadingMode(Object3D.SHADING_FAKED_FLAT);
		RS.setTexture("green");
		RS.setEnvmapped(Object3D.ENVMAP_ENABLED);
		
		FS = Primitives.getSphere(6,5f);
		FS.setTexture("dblue");
		FS.setEnvmapped(Object3D.ENVMAP_ENABLED);
		
		LTS = Primitives.getEllipsoid(6,5f,2);
		LTS.setTexture("red");
		LTS.setEnvmapped(Object3D.ENVMAP_ENABLED);
		
		IB = Primitives.getPyramide(5f);
		IB.setShadingMode(Object3D.SHADING_FAKED_FLAT);
		IB.setTexture("blue");
		IB.setEnvmapped(Object3D.ENVMAP_ENABLED);		
	}
	
	/*
	 * This function will add the neuron to the hashmap of neurons,
	 * clone the existing model for that neuron type,
	 * and then add link the actual model with the id of the neuron.
	 */
	public void addNeuron(Neuron t){
		neurons.put((Integer)t.ID(),t);		
		String nType = t.type();
		Object3D temp=null;
		
		if(nType.equals("RS")){
			temp = 	new Object3D(RS,true);		
		}
		else if(nType.equals("FS")){
			temp = new Object3D(FS,true);
		}
		else if(nType.equals("LTS")){
			temp = new Object3D(LTS,true);
		}
		else if(nType.equals("IB")){
			temp = new Object3D(IB,true);
		}
		temp.translate(new SimpleVector(t.X(),t.Y(),t.Z()));
		
		models.add(temp);
		neuronModels.put((Integer)t.ID(),temp);
	}
	
	
	/*
	 * Creates a connection between 2 neurons based on the scale passed to the function
	 */
	public Object3D connect(Object3D a, Object3D b,float s){
		Object3D temp = new Object3D(10);
		temp.setTexture("gray");
		SimpleVector o = new SimpleVector(a.getTransformedCenter().x,a.getTransformedCenter().y,a.getTransformedCenter().z);
		SimpleVector p = new SimpleVector(b.getTransformedCenter().x,b.getTransformedCenter().y,b.getTransformedCenter().z);
		
		SimpleVector o1 = new SimpleVector(o.x+s,o.y+s,o.z);
		SimpleVector o2 = new SimpleVector(o.x,o.y,o.z);
		SimpleVector o3 = new SimpleVector(o.x-s,o.y-s,o.z);
		
		SimpleVector p1 = new SimpleVector(p.x+s,p.y+s,p.z);
		SimpleVector p2 = new SimpleVector(p.x,p.y,p.z);
		SimpleVector p3 = new SimpleVector(p.x-s,p.y-s,p.z);
		
		temp.addTriangle(o1,o2,o3);
		temp.addTriangle(p1,p2,p3);
		temp.addTriangle(o1,o2,p2);
		temp.addTriangle(o1,p2,p1);
		temp.addTriangle(o1,o3,p1);
		temp.addTriangle(o3,p1,p3);
		temp.addTriangle(o2,o3,p2);
		temp.addTriangle(o3,p2,p3);
		
		temp.setTransparency(0);
		temp.setLighting(Object3D.LIGHTING_NO_LIGHTS);
		temp.setTransparencyMode(Object3D.TRANSPARENCY_MODE_ADD);
		return temp;
	}
	
	public Object3D highlight(int column){
		Object3D temp = new Object3D(12);
		
		SimpleVector a=new SimpleVector((column*200),parser.maxY-20,parser.maxZ+20);
		SimpleVector b=new SimpleVector((column*200),parser.minY+20,parser.maxZ+20);
		SimpleVector c=new SimpleVector((column*200),parser.minY+20,parser.minZ-20);
		SimpleVector d=new SimpleVector((column*200),parser.maxY-20,parser.minZ-20);
		
		SimpleVector a2=new SimpleVector(((column+1)*200),parser.maxY-20,parser.maxZ+20);
		SimpleVector b2=new SimpleVector(((column+1)*200),parser.minY+20,parser.maxZ+20);
		SimpleVector c2=new SimpleVector(((column+1)*200),parser.minY+20,parser.minZ-20);
		SimpleVector d2=new SimpleVector(((column+1)*200),parser.maxY-20,parser.minZ-20);
		
		temp.addTriangle(a,b,c);
		temp.addTriangle(a,d,c);
		temp.addTriangle(a2,b2,c2);
		temp.addTriangle(a2,d2,c2);
		temp.addTriangle(a,a2,b2);
		temp.addTriangle(a,b,b2);
		temp.addTriangle(d,d2,c2);
		temp.addTriangle(d,c,c2);
		temp.addTriangle(a,a2,d);
		temp.addTriangle(d,d2,a2);
		temp.addTriangle(b,b2,c);
		temp.addTriangle(c,c2,b2);
		
		if(column%2==0){temp.setTexture("green");}
		else{temp.setTexture("yellow");}
		
		temp.setCulling(false);
		temp.setLighting(Object3D.LIGHTING_NO_LIGHTS);
		temp.setTransparency(0);
		temp.setTransparencyMode(Object3D.TRANSPARENCY_MODE_ADD);
		
		return temp;
	}
	
	public Object3D divider(int column){
		
		Object3D temp = new Object3D(2);
		
		SimpleVector a=new SimpleVector((column*200),parser.maxY-20,parser.maxZ+20);
		SimpleVector b=new SimpleVector((column*200),parser.minY+20,parser.maxZ+20);
		SimpleVector c=new SimpleVector((column*200),parser.minY+20,parser.minZ-20);
		SimpleVector d=new SimpleVector((column*200),parser.maxY-20,parser.minZ-20);
		
		temp.addTriangle(a,b,c);
		temp.addTriangle(a,d,c);
		
		temp.setTexture("red");
		temp.setCulling(false);
		temp.setLighting(Object3D.LIGHTING_NO_LIGHTS);
		temp.setTransparency(8);
		temp.setTransparencyMode(Object3D.TRANSPARENCY_MODE_ADD);
		
		return temp;
	}
	
	
	public void addModel(Object3D model){
		models.add(model);
	}
	
	public int neuronCount(){return models.size();}
	
	/*public static void main(String[] args) throws InterruptedException{
		new Visual();
	}*/

}

